﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SimpleIR;

namespace PInfoModel.Analyses
{
	public static class CallGraphAnalysisExtractor
	{
		public static Tuple<CallGraphAnalysisInfo, CallGraphAnalysisInfo> ExtractCallGraphInfo(CompleteAnalysisInfoForProgram pip)
		{
			CallGraphAnalysisInfo cginfocomplete = new CallGraphAnalysisInfo(false);
			CallGraphAnalysisInfo cginfouser = new CallGraphAnalysisInfo(true);

			foreach(CallGraphNode cgn in pip.Program.CallGraph.Nodes)
			{
				if(cgn.CallInList.Count == 0)
					continue;

				IRBodyDef cdf = pip.Program.MethodBodyTable.GetBodyForInvokeExactSigMatch(cgn.Def);
				cginfocomplete.ProcessMethodNode(cdf, cgn);
				cginfouser.ProcessMethodNode(cdf, cgn);
			}

			List<List<IRInvokeIdentity>> sccs = pip.Program.CallGraph.ComputeFinalSCCList();
			foreach(List<IRInvokeIdentity> scc in sccs)
			{
				cginfocomplete.ProcessMethodSCC(scc);
				cginfouser.ProcessMethodSCC(scc);
			}

			return new Tuple<CallGraphAnalysisInfo, CallGraphAnalysisInfo>(cginfocomplete, cginfouser);
		}
	}

	/// <summary>
	/// A class that accumulates and manages general info about the final call call graph the 
	/// analysis produces.
	/// </summary>
	public sealed class CallGraphAnalysisInfo
	{
		public readonly bool RestrictedToUserSpace;

		public int TotalMethodsSeen;
		public int TotalCallerEdges;

		public int MaxCallersForMethod;
		public Dictionary<int, int> CallerDistribution;

		public int TotalNumberOfSCCs;
		public int TotalMethodsInSCCs;
		public int MaxSizeSCC;

		public int TotalCallSites;
		public int TotalVirtualCallSites;
		public int TotalMonomorphicCalls;

		public CallGraphAnalysisInfo(bool restrictedtouser)
		{
			this.RestrictedToUserSpace = restrictedtouser;

			this.TotalMethodsSeen = 0;
			this.TotalCallerEdges = 0;

			this.MaxCallersForMethod = Int32.MinValue;
			this.CallerDistribution = new Dictionary<int, int>();

			this.TotalNumberOfSCCs = 0;
			this.TotalMethodsInSCCs = 0;
			this.MaxSizeSCC = Int32.MinValue;

			this.TotalCallSites = 0;
			this.TotalVirtualCallSites = 0;
			this.TotalMonomorphicCalls = 0;
		}

		public void ProcessMethodNode(IRBodyDef b, CallGraphNode cgn)
		{
			if(this.RestrictedToUserSpace & b.InvokeID.DefInClass.TypeDescription.IsBuiltinType)
				return;

			this.TotalMethodsSeen++;
			this.TotalCallerEdges += cgn.CallInList.Count;

			this.MaxCallersForMethod = Math.Max(cgn.CallInList.Count, this.MaxCallersForMethod);
			if(!this.CallerDistribution.ContainsKey(cgn.CallInList.Count))
				this.CallerDistribution.Add(cgn.CallInList.Count, 0);
			this.CallerDistribution[cgn.CallInList.Count] = this.CallerDistribution[cgn.CallInList.Count] + 1;

			if(!(b.IsBuiltin | b.IsAbstract))
			{
				foreach(IRBasicBlock bb in b.Body.BasicBlockList)
				{
					foreach(IRByteCode bc in bb.Operations)
					{
						if(bc is IRCallOp)
						{
							this.TotalCallSites++;

							IRCallOp cop = (IRCallOp)bc;
							if(cop.InvokeSig.IsVirtualInvoke)
							{
								this.TotalVirtualCallSites++;

								if(cop.AnalysisResolvedCallTargets.Count <= 1)
									this.TotalMonomorphicCalls++;
							}
						}
					}
				}
			}
		}

		public void ProcessMethodSCC(List<IRInvokeIdentity> scc)
		{
			bool userscc = scc.Exists((IRInvokeIdentity iiv) => !iiv.DefInClass.TypeDescription.IsBuiltinType);

			if(this.RestrictedToUserSpace & (!userscc))
				return;

			this.TotalNumberOfSCCs++;
			this.TotalMethodsInSCCs += scc.Count;

			this.MaxSizeSCC = Math.Max(scc.Count, this.MaxSizeSCC);
		}

		public string EmitString()
		{
			StringBuilder sb = new StringBuilder(AnalyisSupport.InfoHeader + "CallGraph Analysis Metrics -- " + (this.RestrictedToUserSpace ? "UserSpace" : "FullProgram") + "\n");

			sb.Append(AnalyisSupport.InfoTitle + "Call Graph Sizes\n");
			sb.Append(String.Format("Total Methods: {0}\nTotal CallEdges: {1}\nMax Callers: {2}\nAvg Callers: {3}\n\n", this.TotalMethodsSeen, this.TotalCallerEdges, this.MaxCallersForMethod, ((float)this.TotalCallerEdges) / ((float)this.TotalMethodsSeen)));

			sb.Append(AnalyisSupport.InfoTitle + "Callers Per Method Distribution\n");
			sb.Append("#Callers -> Occourence%\n");
			List<int> ll = new List<int>(this.CallerDistribution.Keys);
			ll.Sort();
			foreach(int cds in ll)
				sb.Append(String.Format("{0} -> {1:p}\n", cds, ((float)this.CallerDistribution[cds]) / ((float)this.TotalMethodsSeen)));
			sb.Append("\n");

			sb.Append(AnalyisSupport.InfoTitle + "SCC Calls\n");
			sb.Append(String.Format("Total SCCs: {0}\nTotal Methods in SCCs: {1}\nMax SCC Size: {2}\nAvg SCC Size: {3}\n\n", this.TotalNumberOfSCCs, this.TotalMethodsInSCCs, this.MaxSizeSCC, ((float)this.TotalMethodsInSCCs) / ((float)this.TotalNumberOfSCCs)));

			sb.Append(AnalyisSupport.InfoTitle + "VCall Info\n");
			sb.Append(String.Format("Total CallSites: {0}\nTotal Virt CallSites: {1}\nVCall Pct: {2:p}\nMonoMorphic VCall Pct: {3:p}\n\n", this.TotalCallSites, this.TotalVirtualCallSites, ((float)this.TotalVirtualCallSites) / ((float)this.TotalCallSites), ((float)this.TotalMonomorphicCalls) / ((float)this.TotalCallSites)));

			return sb.ToString();
		}
	}
}
